/*
 * Swing.java  0.4.0 / Oct 26, 2010
 *
 * Copyright (c) 2010 - 2011, cute4j.com and contributors.
 *
 * This software is licensed under the LGPL version 2.1 license.
 * A copy of this license is included in the file LICENSE-LGPL.txt.
 */
package com.cute4j.swing; // NOPMD

import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import com.cute4j.Cute4j;
import com.cute4j.core.DisposedError;
import com.cute4j.logging.Logger;
import com.cute4j.swing.events.PropertyChangeSupport;
import com.cute4j.swing.graphics.Color;
import com.cute4j.swing.graphics.Dimension;
import com.cute4j.swing.graphics.Font;
import com.cute4j.swing.graphics.Image;
import com.cute4j.swing.graphics.Rectangle;
import com.cute4j.swing.properties.SwingProperties;
import com.cute4j.swing.styles.ColorPalette;
import com.cute4j.swing.styles.FontSet;
import com.cute4j.swing.styles.SystemColorPalette;
import com.cute4j.swing.styles.SystemFontSet;
import com.cute4j.swing.styles.SystemStyle;
import com.cute4j.swing.styles.SystemTheme;
import com.cute4j.swing.styles.Theme;
import com.cute4j.swing.styles.UnsupportedThemeException;
import com.cute4j.util.SimpleWaitCondition;
import com.cute4j.util.Utilities;

/**
 * Cute4j Swing.
 *
 * @cute4j.native
 *    generate="header"
 *    includes="QApplicationAdapter.h,SwingDelegate.h"
 *
 * @author Slobodan Vrkacevic
 * @version 0.4.0
 */
public final class Swing implements SwingProperties {

// Package-Private Static Fields. ----------------------------------------------

  static final Object CLASS_LOCK;

  static boolean running;     // to avoids synthetic methods
  static int exitcode;        // these fields are non private
  static Thread swingThread;  // DO NOT USE THEM outside of this class

// Private Static Fields. ------------------------------------------------------

  private static final PropertyChangeSupport CHANGE_SUPPORT;
  private static final HashMap CUSTOM_PROPERTIES;
  private static boolean initialized;
  private static boolean initializing;
  private static boolean stopped;
  private static boolean disposed;
  private static Locale locale;
  private static String translationFileName;
  private static HashMap installedStyles;
  private static HashMap installedThemes;
  private static Style style;
  private static Theme theme;
  private static boolean useSystemTheme;
  private static StyleSheet styleSheet;
  private static boolean useStyleSheet;

// Static Initialization. ------------------------------------------------------

  static {
    Cute4j.initialize();
    CLASS_LOCK = new Object();
    CHANGE_SUPPORT = new PropertyChangeSupport(Swing.class);
    CUSTOM_PROPERTIES = new HashMap(7);
    locale = Locale.getDefault();
  }

// Private Constructor. --------------------------------------------------------

 /**
  * Class has no instances.
  */
  private Swing() {
    // empty constructor body
  }

// Public Static Methods. ------------------------------------------------------

  /**
   * Returns <tt>true</tt> if Swing is initialized.
   *
   * @return <tt>true</tt> if Swing is initialized
   */
  public static boolean isInitialized() {
    synchronized (CLASS_LOCK) {
      try {
        while (initializing) {
          CLASS_LOCK.wait();
        }
      } catch (InterruptedException e) {
        return false;
      }
      return initialized;
    }
  }

  /**
   * Initializes the library. This method should be called first.
   *
   * @param args the application arguments
   * @param useEventThread if <tt>true</tt> Swing will use separate GUI thread
   */
  public static void initialize(String[] args, boolean useEventThread) {
    synchronized (CLASS_LOCK) {
      disposedCheck();
      if (initialized) {
        return;
      }
      if (initializing) {
        if (Thread.currentThread() != swingThread) { // NOPMD
          try {
            while (initializing) {
              CLASS_LOCK.wait();
            }
          } catch (InterruptedException e) {
            // empty catch block
          }
        }
        return;
      }
      if (!useEventThread) {
        initialized = true;
        if (Cute4j.VERBOSE) {
          Logger.logInfo("Initializing Swing in the main thread");
        }
        initializeLoop(args);
        return;
      }
      initializing = true;
    }
    if (Cute4j.VERBOSE) {
      Logger.logInfo("Initializing Swing in the separate thread");
    }
    SimpleWaitCondition condition = new SimpleWaitCondition();
    SwingEventThread eventThread = new SwingEventThread(args, condition);
    eventThread.start();
    condition.waitFor();
    synchronized (CLASS_LOCK) {
      initialized = true;
      initializing = false;
      CLASS_LOCK.notifyAll();
    }
  }

  /**
   * Initializes the library.
   */
  public static void initialize() {
    initialize(null, false);
  }

  /**
   * Initializes the library.
   *
   * @param useEventThread if <tt>true</tt> Swing will use separate GUI thread
   */
  public static void initialize(boolean useEventThread) {
    initialize(null, useEventThread);
  }

  /**
   * Initializes the library.
   *
   * @param args the application arguments
   */
  public static void initialize(String[] args) {
    initialize(args, false);
  }

  /**
   * Returns <tt>true</tt> if Swing is disposed.
   *
   * @return <tt>true</tt> if Swing is disposed
   */
  public static boolean isDisposed() {
    synchronized (CLASS_LOCK) {
      return disposed;
    }
  }

  /**
   * Disposes the Swing.
   */
  public static void dispose() {
    if (isSwingThread()) {
      disposeLoop();
    } else {
      stopEventLoop(0);
    }
  }

  /**
   * Returns <tt>true</tt> if event loop is running.
   *
   * @return <tt>true</tt> if event loop is running
   */
  public static boolean isEventLoopRunning() {
    synchronized (CLASS_LOCK) {
      return running;
    }
  }

  /**
   * Waits for event loop exit.
   *
   * @throws InterruptedException if interrupted
   */
  public static void waitForEventLoopExit() throws InterruptedException {
    synchronized (CLASS_LOCK) {
      while (running) {
        CLASS_LOCK.wait();
      }
    }
  }

  /**
   * Starts event loop.
   * <p>
   *   <tt>NOTE: This method must be called from the initialization thread!</tt>
   * </p>
   *
   * @throws IllegalThreadStateException if the method is called outside
   *                                     initialization thread
   */
  public static void startEventLoop() {
    synchronized (CLASS_LOCK) {
      initialize();
      if (running) {
        return;
      }
      if (Thread.currentThread() != swingThread) {
        throw new IllegalThreadStateException(
                      "Starting Event Loop outside the initialization thread");
      }
      _flush();
      SwingUtilities.invokeLater(new EventLoopStartup());
    }
    exitcode = _exec();
    synchronized (CLASS_LOCK) {
      running = false;
      disposeLoop();
      CLASS_LOCK.notifyAll();
    }
  }

  /**
   * Returns event loop exit code.
   *
   * @return event loop exit code
   */
  public static int getEventLoopExitCode() {
    return exitcode;
  }

  /**
   * Stops event loop.
   *
   * @param returnCode the event loop return code
   */
  public static void stopEventLoop(int returnCode) {
    synchronized (CLASS_LOCK) {
      if (stopped || !running || !initialized) {
        return;
      }
      stopped = true;
    }
    _exit(returnCode); // thread-safe  // TODO: check this - it is outside synch
  }

  /**
   * Stops event loop.
   */
  public static void stopEventLoop() {
    stopEventLoop(0);
  }

  /**
   * Flushes the event queue.
   */
  public static void flushEvents() {
    synchronized (CLASS_LOCK) {
      disposedCheck();
      if (initialized && !stopped && !initializing) { // thread-safe
        _flush();
      }
    }
  }

  /**
   * Returns <tt>true</tt> if the specified thread is the GUI thread.
   *
   * @param t the thread to be checked
   *
   * @return <tt>true</tt> if the specified thread is the GUI thread
   */
  public static boolean isSwingThread(Thread t) {
    synchronized (CLASS_LOCK) {
      return ((t != null) && (t == swingThread)); // NOPMD
    }
  }

  /**
   * Returns <tt>true</tt> if the current thread is the GUI thread.
   *
   * @return <tt>true</tt> if the current thread is the GUI thread
   */
  public static boolean isSwingThread() {
    synchronized (CLASS_LOCK) {
      return (Thread.currentThread() == swingThread); // NOPMD
    }
  }

  /**
   * Returns locale.
   *
   * @return locale
   */
  public static Locale getLocale() {
    synchronized (CLASS_LOCK) {
      return locale;
    }
  }

  /**
   * Sets locale.
   *
   * @param locale the locale
   */
  public static void setLocale(Locale locale) {
    Locale oldLocale;
    synchronized (CLASS_LOCK) {
      initialize();
      oldLocale = Swing.locale;
      if (oldLocale == null) {
        if (locale == null) {
          return;
        }
      } else {
        if (oldLocale.equals(locale)) {
          return;
        }
      }
      Swing.locale = locale;
    }
    firePropertyChange(PROPERTY_TRANSLATION, oldLocale, locale);
  }

  /**
   * Returns translation file name.
   *
   * @return translation file name
   */
  public static String getTranslation() {
    synchronized (CLASS_LOCK) {
      return translationFileName;
    }
  }

  /**
   * Sets library translation.
   *
   * @param fileName translation file name
   *
   * @return <tt>true</tt> if succeed
   */
  public static boolean setTranslation(String fileName) {
    boolean succeed;
    String oldFileName;
    synchronized (CLASS_LOCK) {
      initialize();
      oldFileName = translationFileName;
      if (oldFileName == null) {
        if (fileName == null) {
          return true;
        }
      } else {
        if (oldFileName.equals(fileName)) {
          return true;
        }
      }
      if (fileName == null) {
        _removeTranslation();
        translationFileName = null;
        succeed = true;
      } else {
        translationFileName = fileName;
        succeed = _setTranslation(fileName);
      }
    }
    if (succeed) {
      firePropertyChange(PROPERTY_TRANSLATION, oldFileName, fileName);
      return true;
    }
    return false;
  }

  /**
   * Removes current translation.
   */
  public static void removeTranslation() {
    setTranslation(null);
  }

  /**
   * Translates the specified text using translation file.
   *
   * @param context the translation context
   * @param sourceText the text to be translated
   *
   * @return translated text
   */
  public static String translate(String context, String sourceText) {
    if (sourceText == null) {
      return null;
    }
    synchronized (CLASS_LOCK) {
      initialize();
      if (translationFileName == null) {
        return sourceText;
      }
      if (context == null) {
        return _translate("Cute4j", sourceText);
      }
      return _translate(context, sourceText);
    }
  }

  /**
   * Translates the specified text using translation file.
   *
   * @param sourceText the text to be translated
   *
   * @return translated text
   */
  public static String translate(String sourceText) {
    return translate(null, sourceText);
  }

  /**
   * Returns installed styles.
   *
   * @return installed styles
   */
  public static Style[] getInstalledStyles() {
    synchronized (CLASS_LOCK) {
      initialize();
      Map installedStyles = getInstalledStylesMap();
      Style[] styles = new Style[installedStyles.size()];
      installedStyles.values().toArray(styles);
      Arrays.sort(styles, StyleComparator.INSTANCE);
      return styles;
    }
  }

  /**
   * Returns the names of the installed styles.
   *
   * @return the names of the installed styles
   */
  public static String[] getInstalledStyleNames() {
    synchronized (CLASS_LOCK) {
      initialize();
      Map installedStyles = getInstalledStylesMap();
      String[] styleNames = new String[installedStyles.size()];
      installedStyles.keySet().toArray(styleNames);
      Arrays.sort(styleNames);
      return styleNames;
    }
  }

  /**
   * Installs the specified style.
   *
   * @param style the style
   *
   * @throws UnsupportedStyleException if the style is not supported
   */
  public static void installStyle(Style style) throws
                                                    UnsupportedStyleException {
    if (style == null) {
      throw new UnsupportedStyleException("Null style");
    }
    String styleName = style.getName();
    if (styleName == null) {
      throw new UnsupportedStyleException("Null style name");
    }
    if (style.isSupported()) {
      synchronized (CLASS_LOCK) {
        initialize();
        getInstalledStylesMap().put(styleName, style);
      }
    } else {
      throw new UnsupportedStyleException("Unsupported style: " +
                                          styleName);
    }
  }

  /**
   * Installs the specified style.
   *
   * @param styleClassName the style class name
   *
   * @throws UnsupportedStyleException if the style class name is invalid
   */
  public static void installStyle(String styleClassName) throws
                                                    UnsupportedStyleException {
    if (styleClassName == null) {
      throw new UnsupportedStyleException("Null style class name");
    }
    try {
      installStyle((Style)Class.forName(styleClassName).newInstance());
    } catch (ClassNotFoundException cmfe) {
      throw new UnsupportedStyleException("Style class \"" +  // NOPMD
                                          styleClassName + "\" not found");
    } catch (InstantiationException ie) { // NOPMD
      throw new UnsupportedStyleException("Invalid style class: " +
                                          styleClassName, ie);
    } catch (IllegalAccessException iae) { // NOPMD
      throw new UnsupportedStyleException("Invalid style class: " +
                                          styleClassName, iae);
    } catch (ClassCastException cce) { // NOPMD
      throw new UnsupportedStyleException("Invalid style class: " +
                                          styleClassName, cce);
    }
  }

  /**
   * Uninstalls the specified style.
   *
   * @param styleName the theme style
   */
  public static void uninstallStyle(String styleName) {
    if (styleName != null) {
      synchronized (CLASS_LOCK) {
        if (installedStyles != null) {
          installedStyles.remove(styleName);
        }
      }
    }
  }

  /**
   * Returns current style.
   *
   * @return current style
   */
  public static Style getStyle() {
    synchronized (CLASS_LOCK) {
      initialize();
      if (style == null) {
        style = new SystemStyle();
      }
      return style;
    }
  }

  /**
   * Returns the name of the current style.
   *
   * @return the name of the current style
   */
  public static String getStyleName() {
    return getStyle().getName();
  }

  /**
   * Sets current style.
   *
   * @param style the style
   *
   * @throws UnsupportedStyleException if the style is not supported
   */
  public static void setStyle(Style style) throws UnsupportedStyleException {
    if (style == null) {
      throw new UnsupportedStyleException("Null style");
    }
    if (style.isSupported()) {
      Style oldStyle;
      Theme theme = null;
      Theme oldTheme = null;
      synchronized (CLASS_LOCK) {
        initialize();
        oldStyle = Swing.style;
        if (oldStyle == null) {
          oldStyle = new SystemStyle();
        } else if (oldStyle.equals(style)) {
          return;
        }
        if (!_setStyle(style.getName())) {
          throw new UnsupportedStyleException();
        }
        Swing.style = style;
        if (!useSystemTheme) {
          theme = style.getTheme();
          if ((theme != null) && theme.isSupported()) {
            oldTheme = Swing.theme;
            if (oldTheme == null) {
              oldTheme = new SystemTheme();
            }
            if (oldTheme.equals(theme)) {
              oldTheme = null;
            } else {
              _setTheme(theme.getColorPalette(), theme.getFontSet());
              Swing.theme = theme;
            }
          }
        }
      }
      firePropertyChange(PROPERTY_STYLE, oldStyle, style);
      if (oldTheme != null) {
        firePropertyChange(PROPERTY_THEME, oldTheme, theme);
      }
    } else {
      throw new UnsupportedStyleException("Unsupported style: " +
                                          style.getName());
    }
  }

  /**
   * Sets current style.
   *
   * @param styleName the style name
   *
   * @throws UnsupportedStyleException if the style name is invalid
   */
  public static void setStyle(String styleName) throws
                                                    UnsupportedStyleException {
    initialize(); // disposed checked in setStyle(style);
    if (styleName == null) {
      throw new UnsupportedStyleException("Null style name");
    }
    Style style;
    synchronized (CLASS_LOCK) {
      style = (Style)getInstalledStylesMap().get(styleName);
    }
    if (style == null) {
      throw new UnsupportedStyleException("Unknown style: " + styleName);
    }
    setStyle(style);
  }

  /**
   * Helper method. Sets style without throwing an exception if failed.
   *
   * @param style the style
   *
   * @return <tt>true</tt> if succeed
   */
  public static boolean setStyleSafely(Style style) {
    try {
      setStyle(style);
      return true;
    } catch (UnsupportedStyleException use) {
      return false;
    }
  }

  /**
   * Helper method. Sets style without throwing an exception if failed.
   *
   * @param styleName the style name
   *
   * @return <tt>true</tt> if succeed
   */
  public static boolean setStyleSafely(String styleName) {
    try {
      setStyle(styleName);
      return true;
    } catch (UnsupportedStyleException use) {
      return false;
    }
  }

  /**
   * Returns installed themes.
   *
   * @return installed themes
   */
  public static Theme[] getInstalledThemes() {
    synchronized (CLASS_LOCK) {
      initialize();
      Map installedThemes = getInstalledThemesMap();
      Theme[] themes = new Theme[installedThemes.size()];
      installedThemes.values().toArray(themes);
      Arrays.sort(themes, ThemeComparator.INSTANCE);
      return themes;
    }
  }

  /**
   * Returns the names of the installed theme.
   *
   * @return the names of the installed theme
   */
  public static String[] getInstalledThemeNames() {
    synchronized (CLASS_LOCK) {
      initialize();
      Map installedThemes = getInstalledThemesMap();
      String[] themeNames = new String[installedThemes.size()];
      installedThemes.keySet().toArray(themeNames);
      Arrays.sort(themeNames);
      return themeNames;
    }
  }

  /**
   * Installs the specified theme.
   *
   * @param theme the theme
   *
   * @throws UnsupportedThemeException if the theme is not supported
   */
  public static void installTheme(Theme theme) throws
                                                    UnsupportedThemeException {
    if (theme == null) {
      throw new UnsupportedThemeException("Null theme");
    }
    String themeName = theme.getName();
    if (themeName == null) {
      throw new UnsupportedThemeException("Null theme name");
    }
    if (theme.isSupported()) {
      synchronized (CLASS_LOCK) {
        initialize();
        getInstalledThemesMap().put(themeName, theme);
      }
    } else {
      throw new UnsupportedThemeException("Unsupported theme: " + themeName);
    }
  }

  /**
   * Installs the specified theme.
   *
   * @param themeClassName the theme class name
   *
   * @throws UnsupportedThemeException if the theme is not supported
   */
  public static void installTheme(String themeClassName) throws
                                                    UnsupportedThemeException {
    if (themeClassName == null) {
      throw new UnsupportedThemeException("Null theme class name");
    }
    try {
      installTheme((Theme)Class.forName(themeClassName).newInstance());
    } catch (ClassNotFoundException cmfe) {
      throw new UnsupportedThemeException("Theme class \"" +  // NOPMD
                                          themeClassName + "\" not found");
    } catch (InstantiationException ie) { // NOPMD
      throw new UnsupportedThemeException("Invalid theme class: " +
                                          themeClassName, ie);
    } catch (IllegalAccessException iae) { // NOPMD
      throw new UnsupportedThemeException("Invalid theme class: " +
                                          themeClassName, iae);
    } catch (ClassCastException cce) { // NOPMD
      throw new UnsupportedThemeException("Invalid theme class: " +
                                          themeClassName, cce);
    }
  }

  /**
   * Uninstalls the specified theme.
   *
   * @param themeName the theme name
   */
  public static void uninstallTheme(String themeName) {
    if (themeName != null) {
      synchronized (CLASS_LOCK) {
        if (installedThemes != null) {
          installedThemes.remove(themeName);
        }
      }
    }
  }

  /**
   * Returns current style.
   *
   * @return current style
   */
  public static Theme getTheme() {
    synchronized (CLASS_LOCK) {
      initialize();
      if (theme == null) {
        theme = new SystemTheme();
      }
      return theme;
    }
  }

  /**
   * Returns the name of the current theme.
   *
   * @return the name of the current theme
   */
  public static String getThemeName() {
    return getTheme().getName();
  }

  /**
   * Sets the theme of the current style.
   *
   * @param theme the theme
   *
   * @throws UnsupportedThemeException if the theme is not supported
   */
  public static void setTheme(Theme theme) throws UnsupportedThemeException {
    if (!useSystemTheme) {
      if (theme == null) {
        throw new UnsupportedThemeException("Null theme");
      }
      if (theme.isSupported()) {
        Theme oldTheme;
        synchronized (CLASS_LOCK) {
          initialize();
          oldTheme = Swing.theme;
          if (oldTheme == null) {
            oldTheme = new SystemTheme();
          } else if (oldTheme.equals(theme)) {
            return;
          }
          _setTheme(theme.getColorPalette(), theme.getFontSet());
          Swing.theme = theme;
        }
        firePropertyChange(PROPERTY_THEME, oldTheme, theme);
      } else {
        throw new UnsupportedThemeException("Unsupported theme: " +
                                            theme.getName());
      }
    }
  }

  /**
   * Sets the theme of the current style.
   *
   * @param themeName the theme name
   *
   * @throws UnsupportedThemeException if the theme class name is invalid
   */
  public static void setTheme(String themeName) throws
                                                    UnsupportedThemeException {
    initialize(); // disposed checked in setTheme(theme);
    if (themeName == null) {
      throw new UnsupportedThemeException("Null theme name");
    }
    Theme theme;
    synchronized (CLASS_LOCK) {
      theme = (Theme)getInstalledThemesMap().get(themeName);
    }
    if (theme == null) {
      throw new UnsupportedThemeException("Unknown theme: " + themeName);
    }
    setTheme(theme);
  }

  /**
   * Helper method. Sets theme without throwing an exception if failed.
   *
   * @param theme the theme
   *
   * @return <tt>true</tt> if succeed
   */
  public static boolean setThemeSafely(Theme theme) {
    try {
      setTheme(theme);
      return true;
    } catch (UnsupportedThemeException ute) {
      return false;
    }
  }

  /**
   * Helper method. Sets theme without throwing an exception if failed.
   *
   * @param themeName the theme name
   *
   * @return <tt>true</tt> if succeed
   */
  public static boolean setThemeSafely(String themeName) {
    try {
      setTheme(themeName);
      return true;
    } catch (UnsupportedThemeException ute) {
      return false;
    }
  }

  /**
   * Returns <tt>true</tt> if Swing is using the system style theme.
   *
   * @return <tt>true</tt> if Swing is using the system style theme
   */
  public static boolean isUsingSystemTheme() {
    synchronized (CLASS_LOCK) {
      return useSystemTheme;
    }
  }

  /**
   * Sets whether or not Swing should use system style theme.
   *
   * @param useSystemTheme if <tt>true</tt> Swing will use system style theme
   */
  public static void setUseSystemTheme(boolean useSystemTheme) {
    synchronized (CLASS_LOCK) {
      initialize();
      if (Swing.useSystemTheme != useSystemTheme) {
        _setUseSystemTheme(useSystemTheme);
        Swing.useSystemTheme = useSystemTheme;
        if (useSystemTheme) {
          Theme oldTheme = Swing.theme;
          if (!(oldTheme instanceof SystemTheme)) {
            theme = new SystemTheme();
            firePropertyChange(PROPERTY_THEME, oldTheme, theme);
          }
        }
      }
    }
  }

  /**
   * Returns style sheet.
   *
   * @return style sheet
   */
  public static StyleSheet getStyleSheet() {
    synchronized (CLASS_LOCK) {
      return styleSheet;
    }
  }

  /**
   * Sets style sheet.
   *
   * @param styleSheet the style sheet
   */
  public static void setStyleSheet(StyleSheet styleSheet) {
    StyleSheet oldStyleSheet;
    synchronized (CLASS_LOCK) {
      initialize();
      oldStyleSheet = Swing.styleSheet;
      if (oldStyleSheet == null) {
        if (styleSheet == null) {
          return;
        }
      } else {
        if (oldStyleSheet.equals(styleSheet)) {
          return;
        }
      }
      if ((styleSheet == null) || !styleSheet.isValid()) {
        _setStyleSheet(null);
        Swing.styleSheet = null;
        useStyleSheet = false;
      } else {
        _setStyleSheet(styleSheet.getStyleSheetText());
        Swing.styleSheet = styleSheet;
        useStyleSheet = true;
      }
    }
    firePropertyChange(PROPERTY_STYLE_SHEET, oldStyleSheet, styleSheet);
  }

  /**
   * Sets style sheet.
   *
   * @param styleSheetText the style sheet text
   */
  public static void setStyleSheet(String styleSheetText) {
    setStyleSheet(new StyleSheet(styleSheetText));
  }

  /**
   * Sets style sheet.
   *
   * @param styleSheetFile the style sheet file
   *
   * @throws IOException on an I/O error
   */
  public static void setStyleSheet(File styleSheetFile) throws IOException {
    setStyleSheet(new StyleSheet(styleSheetFile));
  }

  /**
   * Sets style sheet.
   *
   * @param styleSheetStream the style sheet input stream
   *
   * @throws IOException on an I/O error
   */
  public static void setStyleSheet(InputStream styleSheetStream)
                                                           throws IOException {
    setStyleSheet(new StyleSheet(styleSheetStream));
  }

  /**
   * Sets style sheet.
   *
   * @param styleSheetFile the style sheet file
   *
   * @return <tt>true</tt> if succeed
   */
  public static boolean setStyleSheetSafely(String styleSheetFile) {
    try {
      setStyleSheet(new StyleSheet(new File(styleSheetFile)));
      return true;
    } catch (IOException ex) {
      return false;
    }
  }

  /**
   * Sets style sheet.
   *
   * @param styleSheetStream the style sheet input stream
   *
   * @return <tt>true</tt> if succeed
   */
  public static boolean setStyleSheetSafely(InputStream styleSheetStream) {
    try {
      setStyleSheet(new StyleSheet(styleSheetStream));
      return true;
    } catch (IOException ex) {
      return false;
    }
  }

  /**
   * Removes the current style sheet.
   */
  public static void removeStyleSheet() {
    setStyleSheet((StyleSheet)null);
  }

  /**
   * Sets the application override cursor (cursor that indicates special state
   * of the application).
   *
   * @param cursor the cursor
   */
  public static void setOverrideCursor(Cursor cursor) {
    if (cursor != null) {
      synchronized (CLASS_LOCK) {
        initialize();
        _setOverrideCursor(cursor.type);
      }
    }
    // doesn't use event notification
  }

  /**
   * Changes the application override cursor (cursor that indicates special
   * state of the application).
   *
   * @param cursor the cursor
   */
  public static void changeOverrideCursor(Cursor cursor) {
    if (cursor != null) {
      synchronized (CLASS_LOCK) {
        initialize();
        _changeOverrideCursor(cursor.type);
      }
    }
    // doesn't use event notification
  }

  /**
   * Undoes the last setOverrideCursor() call.
   */
  public static void restoreOverrideCursor() {
    synchronized (CLASS_LOCK) {
      initialize();
      _restoreOverrideCursor();
    }
    // doesn't use event notification
  }

  /**
   * Creates and returns image.
   *
   * @param imageData the image data
   * @param length the image data length
   *
   * @return image
   */
  public static Image crateImage(byte[] imageData, int length) {
    synchronized (CLASS_LOCK) {
      initialize();
      try {
        return new SwingImage(imageData, length);
      } catch (Throwable t) {
        if (Logger.isErrorEnabled()) {
          Logger.logWarning("Image creation failed", t);
        }
        return SwingImage.getEmptyImage();
      }
    }
  }

  /**
   * Creates and returns image.
   *
   * @param imageData the image data
   *
   * @return image
   */
  public static Image crateImage(byte[] imageData) {
    synchronized (CLASS_LOCK) {
      initialize();
      if (imageData == null) {
        if (Logger.isErrorEnabled()) {
          Logger.logWarning("Image creation failed: null image data"); // NOPMD
        }
        return SwingImage.getEmptyImage();
      }
      return crateImage(imageData, imageData.length);
    }
  }

  /**
   * Creates and returns image.
   *
   * @param location the image location
   *
   * @return image
   */
  public static Image crateImage(URL location) {
    synchronized (CLASS_LOCK) {
      initialize();
      try {
        return new SwingImage(location);
      } catch (Throwable t) {
        if (Logger.isErrorEnabled()) {
          Logger.logWarning("Image creation failed: " + location + " location",
                            t);
        }
        return SwingImage.getEmptyImage();
      }
    }
  }

  /**
   * Creates and returns image.
   *
   * @param fileName the image file name
   *
   * @return image
   */
  public static Image crateImage(String fileName) {
    synchronized (CLASS_LOCK) {
      initialize();
      try {
        return new SwingImage(fileName);
      } catch (Throwable t) {
        if (Logger.isErrorEnabled()) {
          Logger.logWarning("Image creation failed: " + fileName + " name", t);
        }
        return SwingImage.getEmptyImage();
      }
    }
  }

  /**
   * Creates and returns image.
   *
   * @param file the image file
   *
   * @return image
   */
  public static Image crateImage(File file) {
    synchronized (CLASS_LOCK) {
      initialize();
      try {
        return new SwingImage(file);
      } catch (Throwable t) {
        if (Logger.isErrorEnabled()) {
          Logger.logWarning("Image creation failed: " + file + " file", t);
        }
        return SwingImage.getEmptyImage();
      }
    }
  }

  /**
   * Shows information about Swing.
   */
  public static void about() {
    synchronized (CLASS_LOCK) {
      initialize();
      _aboutCute4j(Cute4j.VERSION_STRING, Cute4j.DATE_STRING);
    }
  }

  /**
   * Shows information about Qt.
   */
  public static void aboutQt() {
    synchronized (CLASS_LOCK) {
      initialize();
      _aboutQt();
    }
  }

  /**
   * Returns custom property.
   *
   * @param name the property name
   *
   * @return custom property
   */
  public static String getCustomProperty(String name) {
    synchronized (CLASS_LOCK) {
      if (name == null) {
        throw new IllegalArgumentException("Null name");
      }
      return (String)CUSTOM_PROPERTIES.get(name);
    }
  }

  /**
   * Sets custom property.
   *
   * @param name the property name
   * @param value the property value
   */
  public static void setCustomProperty(String name, String value) {
    String oldValue;
    boolean propertyChanged;
    synchronized (CLASS_LOCK) {
      initialize();
      if (name == null) {
        throw new IllegalArgumentException("Null name");
      }
      if (value == null) {
        oldValue = (String)CUSTOM_PROPERTIES.remove(name);
      } else {
        oldValue = (String)CUSTOM_PROPERTIES.put(name, value);
      }
      propertyChanged = !Utilities.equals(oldValue, value);
      if (propertyChanged) {
        disposedCheck();
        _setCustomProperty(name, value);
      }
    }
    if (propertyChanged) {
      firePropertyChange(PROPERTY_CUSTOM, oldValue, value);
    }
  }

  /**
   * Adds property change listener for all properties.
   *
   * @param pcl the property change listener
   */
  public static void addPropertyChangeListener(PropertyChangeListener pcl) {
    CHANGE_SUPPORT.addPropertyChangeListener(pcl);
  }

  /**
   * Adds property change listener for specified property.
   *
   * @param propertyName the property name
   * @param pcl the property change listener
   */
  public static void addPropertyChangeListener(String propertyName,
                                               PropertyChangeListener pcl) {
    CHANGE_SUPPORT.addPropertyChangeListener(propertyName, pcl);
  }

  /**
   * Removes property change listener for all properties.
   *
   * @param pcl the property change listener
   */
  public static void removePropertyChangeListener(PropertyChangeListener pcl) {
    CHANGE_SUPPORT.removePropertyChangeListener(pcl);
  }

  /**
   * Removes property change listener for specified property.
   *
   * @param propertyName the property name
   * @param pcl the property change listener
   */
  public static void removePropertyChangeListener(String propertyName,
                                                  PropertyChangeListener pcl) {
    CHANGE_SUPPORT.removePropertyChangeListener(propertyName, pcl);
  }

  /**
   * Returns property change listeners for all properties.
   *
   * @return property change listeners for all properties
   */
  public static PropertyChangeListener[] getPropertyChangeListeners() {
    return CHANGE_SUPPORT.getPropertyChangeListeners();
  }

  /**
   * Returns property change listeners for specified property.
   *
   * @param propertyName the property name
   *
   * @return property change listeners for specified property
   */
  public static PropertyChangeListener[] getPropertyChangeListeners(
                                                         String propertyName) {
    return CHANGE_SUPPORT.getPropertyChangeListeners(propertyName);
  }

// Package-Private Static Methods. ---------------------------------------------

  /**
   * Throws DisposedError exception if Swing is disposed.
   */
  static void disposedCheck() {
    synchronized (CLASS_LOCK) {
      if (disposed) {
        throw new DisposedError("Swing is disposed");
      }
    }
  }
  
  static void initializeLoop(String[] args) { // thread-safe
    swingThread = Thread.currentThread();
    Runtime.getRuntime().addShutdownHook(new Cute4jShutdownHook());
    _initialize(args, Color.class, Dimension.class, Rectangle.class, Font.class,
                SwingImage.class, SwingIconSet.class, Runnable.class,
                InvocationTargetException.class, SystemColorPalette.class,
                SystemFontSet.class);
  }

  static void disposeLoop() {
    synchronized (CLASS_LOCK) {
      if (disposed) {
        return;
      }
      try {
        while (initializing) {
          CLASS_LOCK.wait();
        }
      } catch (InterruptedException e) {
        return;
      }
      disposed = true;
      stopped = true; // thread-safe
      swingThread = null;
    }
    _dispose();
    if (Cute4j.VERBOSE) {
      Logger.logInfo("Swing disposed");
    }
  }

  static boolean useStyleSheet() {
    synchronized (CLASS_LOCK) {
      if (disposed) { // little opt. - frequently used
        throw new DisposedError("Swing is disposed");
      }
      return useStyleSheet;
    }
  }

// Private Static Methods. -----------------------------------------------------

  private static void firePropertyChange(String propertyName, Object oldValue,
                                         Object newValue) {
    CHANGE_SUPPORT.firePropertyChange(propertyName, oldValue, newValue);
  }

  private static Map getInstalledStylesMap() { // always under lock
    if (installedStyles == null) {
      Style[] availableStyles = _availableStyles(SystemStyle.class);
      HashMap installedStyles = new HashMap(availableStyles.length);
      for (int i = 0; i < availableStyles.length; i++) {
        installedStyles.put(availableStyles[i].getName(), availableStyles[i]);
      }
      Swing.installedStyles = installedStyles;
    }
    return installedStyles;
  }

  private static Map getInstalledThemesMap() { // always under lock
    if (installedThemes == null) {
      Theme[] availableThemes = _availableThemes(SystemTheme.class);
      HashMap installedThemes = new HashMap(availableThemes.length);
      for (int i = 0; i < availableThemes.length; i++) {
        installedThemes.put(availableThemes[i].getName(), availableThemes[i]);
      }
      Swing.installedThemes = installedThemes;
    }
    return installedThemes;
  }

// Package-Private Static Native Methods. --------------------------------------

  static native void _dispose();

  static native int _exec();

  static native void _exit(int returnCode);

  static native void _flush();

  static native void _disableNativeLogging();

  static native boolean _setTranslation(String fileName);

  static native void _removeTranslation();

  static native String _translate(String context, String sourceText);

  static native Style[] _availableStyles(Class systemStyleClass);

  static native boolean _setStyle(String name);

  static native void _setStyleSheet(String styleSheetText);

  static native void _setOverrideCursor(int type);

  static native void _changeOverrideCursor(int type);

  static native void _restoreOverrideCursor();

  static native Theme[] _availableThemes(Class systemThemeClass);

  static native void _setTheme(ColorPalette colorPalette, FontSet fontSet);

  static native void _setUseSystemTheme(boolean useSystemTheme);

  static native void _setCustomProperty(String name, String value);

  static native void _aboutQt();

  static native void _aboutCute4j(String version, String date);

// Private Static Native Methods. ----------------------------------------------

  private static native void _initialize(String[] args, Class colorClass,
                                         Class dimensionClass,
                                         Class rectangleClass, Class fontClass,
                                         Class imageClass, Class iconSetClass,
                                         Class runnableClass, Class iteClass,
                                         Class systemColorPaletteClass,
                                         Class systemFontSetClass);
 
// Inner Classes. --------------------------------------------------------------

  /**
   * JVM Shutdown Hook.
   */
  static final class Cute4jShutdownHook extends Thread {
    public Cute4jShutdownHook() {
      super("Cute4j Shutdown Hook");
    }
    public void run() {
      stopEventLoop(0);
    }
    public synchronized void start() { // NOPMD
      if (Thread.currentThread() == swingThread) { // thread-safe
        run(); // NOFB
      } else {
        // In some irregular cases it is impossible to dispose native loop
        // properly (no standard TLS support), thus warnings are ignored.
        _disableNativeLogging();
        super.start();
        try {
          swingThread.join(); // thread-safe
        } catch (Throwable t) { // swingThread can be null NOPMD
          // empty catch block
        }
      }
      disposeLoop();
    }
  }

  /**
   * Swing Event Thread.
   */
  static final class SwingEventThread extends Thread {
    private String[] args;
    private SimpleWaitCondition condition;
    public SwingEventThread(String[] args, SimpleWaitCondition condition) {
      super("SwingEventThread");
      if (args != null) {
        this.args = (String[])args.clone();
      }
      this.condition = condition;
    }
    public void run() {
      synchronized (CLASS_LOCK) {
        initializeLoop(args);
        args = null;
        _flush();
        SwingUtilities.invokeLater(new EventLoopStartup(condition));
        condition = null;
      }
      exitcode = _exec();
      synchronized (CLASS_LOCK) {
        running = false;
        disposeLoop();
        CLASS_LOCK.notifyAll();
      }
    }
  }

  /**
   * Event Loop Synchronizer.
   */
  static final class EventLoopStartup implements Runnable {
    private final SimpleWaitCondition condition;
    public EventLoopStartup() {
      condition = null;
    }
    public EventLoopStartup(SimpleWaitCondition condition) {
      this.condition = condition;
    }
    public void run() {
      synchronized (CLASS_LOCK) {
        running = true;
        // there's no need for lock.notifyAll();
      }
      if (Cute4j.VERBOSE) {
        Logger.logInfo("Swing event loop started");
      }
      if (condition != null) {
        condition.signal();
      }
    }
  }

  /**
   * Style Comparator.
   */
  static final class StyleComparator implements Comparator {
    static final StyleComparator INSTANCE = new StyleComparator();
    public boolean equals(Object obj) {
      return (obj == this);
    }
    public int hashCode() {
      return System.identityHashCode(this);
    }
    public int compare(Object o1, Object o2) {
      return ((Style)o1).getName().compareTo(((Style)o2).getName());
    }
  }

  /**
   * Theme Comparator.
   */
  static final class ThemeComparator implements Comparator {
    static final ThemeComparator INSTANCE = new ThemeComparator();
    public boolean equals(Object obj) {
      return (obj == this);
    }
    public int hashCode() {
      return System.identityHashCode(this);
    }
    public int compare(Object o1, Object o2) {
      return ((Theme)o1).getName().compareTo(((Theme)o2).getName());
    }
  }

// Main Method. ----------------------------------------------------------------

  /**
   * Displays the info about the library
   */
  public static void main(String[] args) {
    Swing.about(); // TODO: check how does it exit
  }

}
